﻿using System;
using System.Collections.Generic;
using System.IO;
using log4net;
using Mono.Cecil;
using System.Text;
using Mono.Cecil.Binary;
using System.Collections;
using System.Diagnostics;

namespace CILADA.Engine
{
    public class CompileTimeDependencyAnalyser : AnalyserBase
    {
        #region Fields
        private IDictionary<string, TypeInfo> _dependencies;
        public static readonly string DEFAULT_OUTPUT_DIR = "results";
        private static readonly ILog log = LogManager.GetLogger(typeof(CompileTimeDependencyAnalyser));
        #endregion

        #region Properties
        public bool Successful { get; private set; }
        public IDictionary<string, TypeInfo> Dependencies
        {
            get { return _dependencies; }
            private set { _dependencies = value; }
        }
        #endregion

        #region Constructor
        public CompileTimeDependencyAnalyser(string outputDirectory, string outputFilename)
        {
            // null checks
            if (null == outputDirectory) throw new ArgumentNullException("output directory cannot be null.");

            this.OutputDirectory = outputDirectory;
            this.OutputFileName = outputFilename;

            ClearAll();
        }
        #endregion

        #region Instance Methods
        public void ClearDependencies()
        {
            this._dependencies = new Dictionary<string, TypeInfo>();
            this._assemReader = new AssemblyReader();
        }

        public void ClearAll()
        {
            ClearStats();
            this.Successful = false;
            ClearDependencies();
        }

        public override string AnalyseAssemblies(ICollection<string> assemblyPaths)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();

            ClearAll();

            foreach (string assembly in assemblyPaths)
            {
                _assemReader.Container = String.Empty;
                AnalyseAssembly(assembly, false);
            }

            TimeSpan analysisTimeSpan = sw.Elapsed;
            sw.Stop();

            //WriteOutput(_dependencies.Values, this.OutputFileName);
            return this.GetAnalysisResults(assemblyPaths.Count, "Compile Time", analysisTimeSpan);
        }

        public override void AnalyseAssembly(string assemblyPath)
        {
            AnalyseAssembly(assemblyPath, true);
        }

        private void AnalyseAssembly(string assemblyPath, bool singleAnalysis)
        {
            log.Info("Analysing assembly " + assemblyPath);
            AssemblyAnalysisResult result = AssemblyAnalysisResult.Unknown;
            FileInfo fi = new FileInfo(assemblyPath);

            AssemblyDefinition assemblyDef = null;
            try
            {
                assemblyDef = _assemReader.LoadAssembly(assemblyPath);
            }
            catch (BadImageFormatException)
            {
                result = AssemblyAnalysisResult.NotDotNetAssembly;
                NotDotNetAssemblyCount++;
            }
            catch (FileNotFoundException)
            {
                result = AssemblyAnalysisResult.AssemblyNotFound;
                AssemblyNotFoundCount++;
            }

            if (null != assemblyDef)
            {
                try
                {
                    _assemReader.Container = fi.Name;
                    _assemReader.GetDepedenciesForAssembly(assemblyDef, ref _dependencies);
                    if (singleAnalysis)
                    {
                        //WriteOutput(this._dependencies.Values, fi.Name);
                        ClearDependencies();
                    }

                    result = AssemblyAnalysisResult.AnalysisSuccessful;
                }
                catch (Exception e)
                {
                    result = AssemblyAnalysisResult.ErrorDuringProcessing;
                    ErrorDuringProcessingCount++;
                    log.Error("Error while processing assembly " + fi.FullName, e);
                    throw;
                }
            }

            // fire AssemblyAnalysed event
            AssemblyAnalysedEventArgs aae = new AssemblyAnalysedEventArgs(result, fi.FullName, null);
            if (AssemblyAnalysisResult.AnalysisSuccessful == result && null != assemblyDef)
            {
                aae.AssemblyProperties.Add("Name", GetStringOrNull(assemblyDef.Name));
                aae.AssemblyProperties.Add("Kind", GetStringOrNull(assemblyDef.Kind));
                aae.AssemblyProperties.Add("EntryPoint", GetStringOrNull(assemblyDef.EntryPoint));
            }
            OnAssemblyAnalysed(aae);
        }

        //private OutputResult WriteOutput(ICollection<TypeInfo> dependencies, string outputFileName)
        //{
        //    DirectoryInfo di = new DirectoryInfo(this.OutputDirectory);
        //    if (!di.Exists)
        //    {
        //        Directory.CreateDirectory(di.FullName);
        //        log.Debug("Creating output directory " + di.FullName);
        //    }

        //    log.Debug("Writing output to " + this.OutputDirectory + @"\" + outputFileName);
        //    OutputResult oR = GraphMLFormat.WriteCompileTimeDependencies(dependencies, this.OutputDirectory, outputFileName + "_CT");
        //    return oR;
        //}
        #endregion
    }
}
